package com.easyaop.commons.asm;

import java.lang.invoke.MethodType;
import java.util.Arrays;

import org.objectweb.asm.Attribute;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import com.easyaop.commons.ASMOpcodes;
import com.easyaop.commons.Constants;
import com.easyaop.commons.Wrapper;
import com.easyaop.commons.utils.TypeUtils;

public class CodeEmitter extends LocalVariablesSorter {
    private static final Signature CSTRUCT_NULL = TypeUtils.parseConstructor();

    private static final Signature CSTRUCT_STRING = TypeUtils.parseConstructor(String.class);

    public static final int ADD = ASMOpcodes.IADD;

    public static final int MUL = ASMOpcodes.IMUL;

    public static final int XOR = ASMOpcodes.IXOR;

    public static final int USHR = ASMOpcodes.IUSHR;

    public static final int SUB = ASMOpcodes.ISUB;

    public static final int DIV = ASMOpcodes.IDIV;

    public static final int NEG = ASMOpcodes.INEG;

    public static final int REM = ASMOpcodes.IREM;

    public static final int AND = ASMOpcodes.IAND;

    public static final int OR = ASMOpcodes.IOR;

    public static final int GT = ASMOpcodes.IFGT;

    public static final int LT = ASMOpcodes.IFLT;

    public static final int GE = ASMOpcodes.IFGE;

    public static final int LE = ASMOpcodes.IFLE;

    public static final int NE = ASMOpcodes.IFNE;

    public static final int EQ = ASMOpcodes.IFEQ;

    private ClassEmitter ce;

    private State state;

    private static class State extends MethodInfo {
        ClassInfo classInfo;

        int access;

        Signature sig;

        Type[] argumentTypes;

        int localOffset;

        Type[] exceptionTypes;

        State(ClassInfo classInfo, int access, Signature sig, Type[] exceptionTypes) {
            this.classInfo = classInfo;
            this.access = access;
            this.sig = sig;
            this.exceptionTypes = exceptionTypes;
            localOffset = TypeUtils.isStatic(access) ? 0 : 1;
            argumentTypes = sig.getArgumentTypes();
        }

        public ClassInfo getClassInfo() {
            return classInfo;
        }

        public int getModifiers() {
            return access;
        }

        public Signature getSignature() {
            return sig;
        }

        public Type[] getExceptionTypes() {
            return exceptionTypes;
        }

        public Attribute getAttribute() {
            // TODO
            return null;
        }
    }

    CodeEmitter(ClassEmitter ce, MethodVisitor mv, int access, Signature sig, Type[] exceptionTypes) {
        super(access, sig.getDescriptor(), mv);
        this.ce = ce;
        state = new State(ce.getClassInfo(), access, sig, exceptionTypes);
    }

    public CodeEmitter(CodeEmitter wrap) {
        super(wrap);
        this.ce = wrap.ce;
        this.state = wrap.state;
    }

    public boolean isStaticHook() {
        return false;
    }

    public Signature getSignature() {
        return state.sig;
    }

    public Type getReturnType() {
        return state.sig.getReturnType();
    }

    public MethodInfo getMethodInfo() {
        return state;
    }

    public ClassEmitter getClassEmitter() {
        return ce;
    }

    public void end_method() {
        visitMaxs(0, 0);
    }

    public Block begin_block() {
        return new Block(this);
    }

    public void catch_exception(Block block, Type exception) {
        catch_exception(block.getStart(), block.getEnd(), mark(), exception);
    }

    public void catch_exception(Label start, Label end, Label handle, Type exception) {
        if (start == null) {
            throw new IllegalStateException("end of block is unset");
        }
        super.visitTryCatchBlock(start,
                end,
                handle,
                exception.getInternalName());
    }

    public void catch_exception(Label start, Label end, Label handle, Class<?> exception) {
        catch_exception(start, end, handle, Type.getType(exception));
    }

    public void goTo(Label label) {
        super.visitJumpInsn(ASMOpcodes.GOTO, label);
    }

    public void ifnull(Label label) {
        super.visitJumpInsn(ASMOpcodes.IFNULL, label);
    }

    public void ifnonnull(Label label) {
        super.visitJumpInsn(ASMOpcodes.IFNONNULL, label);
    }

    public void if_jump(int mode, Label label) {
        super.visitJumpInsn(mode, label);
    }

    public void if_icmp(int mode, Label label) {
        if_cmp(Type.INT_TYPE, mode, label);
    }

    public void if_cmp(Type type, int mode, Label label) {
        int intOp = -1;
        int jumpmode = mode;
        switch (mode) {
            case GE:
                jumpmode = LT;
                break;
            case LE:
                jumpmode = GT;
                break;
        }
        switch (type.getSort()) {
            case Type.LONG:
                super.visitInsn(ASMOpcodes.LCMP);
                break;
            case Type.DOUBLE:
                super.visitInsn(ASMOpcodes.DCMPG);
                break;
            case Type.FLOAT:
                super.visitInsn(ASMOpcodes.FCMPG);
                break;
            case Type.ARRAY:
            case Type.OBJECT:
                switch (mode) {
                    case EQ:
                        super.visitJumpInsn(ASMOpcodes.IF_ACMPEQ, label);
                        return;
                    case NE:
                        super.visitJumpInsn(ASMOpcodes.IF_ACMPNE, label);
                        return;
                }
                throw new IllegalArgumentException("Bad comparison for type " + type);
            default:
                switch (mode) {
                    case EQ:
                        intOp = ASMOpcodes.IF_ICMPEQ;
                        break;
                    case NE:
                        intOp = ASMOpcodes.IF_ICMPNE;
                        break;
                    case GE:
                        swap(); /* fall through */
                    case LT:
                        intOp = ASMOpcodes.IF_ICMPLT;
                        break;
                    case LE:
                        swap(); /* fall through */
                    case GT:
                        intOp = ASMOpcodes.IF_ICMPGT;
                        break;
                }
                super.visitJumpInsn(intOp, label);
                return;
        }
        if_jump(jumpmode, label);
    }

    public void pop() {
        super.visitInsn(ASMOpcodes.POP);
    }

    public void pop2() {
        super.visitInsn(ASMOpcodes.POP2);
    }

    public void dup() {
        super.visitInsn(ASMOpcodes.DUP);
    }

    public void dup2() {
        super.visitInsn(ASMOpcodes.DUP2);
    }

    public void dup_x1() {
        super.visitInsn(ASMOpcodes.DUP_X1);
    }

    public void dup_x2() {
        super.visitInsn(ASMOpcodes.DUP_X2);
    }

    public void dup2_x1() {
        super.visitInsn(ASMOpcodes.DUP2_X1);
    }

    public void dup2_x2() {
        super.visitInsn(ASMOpcodes.DUP2_X2);
    }

    public void swap() {
        super.visitInsn(ASMOpcodes.SWAP);
    }

    public void aconst_null() {
        super.visitInsn(ASMOpcodes.ACONST_NULL);
    }

    public void swap(Type prev, Type type) {
        if (type.getSize() == 1) {
            if (prev.getSize() == 1) {
                swap(); // same as dup_x1(), pop();
            } else {
                dup_x2();
                pop();
            }
        } else {
            if (prev.getSize() == 1) {
                dup2_x1();
                pop2();
            } else {
                dup2_x2();
                pop2();
            }
        }
    }

    public void monitorenter() {
        super.visitInsn(ASMOpcodes.MONITORENTER);
    }

    public void monitorexit() {
        super.visitInsn(ASMOpcodes.MONITOREXIT);
    }

    public void math(int op, Type type) {
        super.visitInsn(type.getOpcode(op));
    }

    public void array_load(Type type) {
        super.visitInsn(type.getOpcode(ASMOpcodes.IALOAD));
    }

    public void array_store(Type type) {
        super.visitInsn(type.getOpcode(ASMOpcodes.IASTORE));
    }

    public void array_store(Class<?> type) {
        array_store(Type.getType(type));
    }

    /**
     * Casts from one primitive numeric type to another
     */
    public void cast_numeric(Type from, Type to) {
        if (from != to) {
            if (from == Type.DOUBLE_TYPE) {
                if (to == Type.FLOAT_TYPE) {
                    super.visitInsn(ASMOpcodes.D2F);
                } else if (to == Type.LONG_TYPE) {
                    super.visitInsn(ASMOpcodes.D2L);
                } else {
                    super.visitInsn(ASMOpcodes.D2I);
                    cast_numeric(Type.INT_TYPE, to);
                }
            } else if (from == Type.FLOAT_TYPE) {
                if (to == Type.DOUBLE_TYPE) {
                    super.visitInsn(ASMOpcodes.F2D);
                } else if (to == Type.LONG_TYPE) {
                    super.visitInsn(ASMOpcodes.F2L);
                } else {
                    super.visitInsn(ASMOpcodes.F2I);
                    cast_numeric(Type.INT_TYPE, to);
                }
            } else if (from == Type.LONG_TYPE) {
                if (to == Type.DOUBLE_TYPE) {
                    super.visitInsn(ASMOpcodes.L2D);
                } else if (to == Type.FLOAT_TYPE) {
                    super.visitInsn(ASMOpcodes.L2F);
                } else {
                    super.visitInsn(ASMOpcodes.L2I);
                    cast_numeric(Type.INT_TYPE, to);
                }
            } else {
                if (to == Type.BYTE_TYPE) {
                    super.visitInsn(ASMOpcodes.I2B);
                } else if (to == Type.CHAR_TYPE) {
                    super.visitInsn(ASMOpcodes.I2C);
                } else if (to == Type.DOUBLE_TYPE) {
                    super.visitInsn(ASMOpcodes.I2D);
                } else if (to == Type.FLOAT_TYPE) {
                    super.visitInsn(ASMOpcodes.I2F);
                } else if (to == Type.LONG_TYPE) {
                    super.visitInsn(ASMOpcodes.I2L);
                } else if (to == Type.SHORT_TYPE) {
                    super.visitInsn(ASMOpcodes.I2S);
                }
            }
        }
    }

    public void push(int i) {
        if (i < -1) {
            super.visitLdcInsn(new Integer(i));
        } else if (i <= 5) {
            super.visitInsn(TypeUtils.ICONST(i));
        } else if (i <= Byte.MAX_VALUE) {
            super.visitIntInsn(ASMOpcodes.BIPUSH, i);
        } else if (i <= Short.MAX_VALUE) {
            super.visitIntInsn(ASMOpcodes.SIPUSH, i);
        } else {
            super.visitLdcInsn(new Integer(i));
        }
    }

    public void push(long value) {
        if (value == 0L || value == 1L) {
            super.visitInsn(TypeUtils.LCONST(value));
        } else {
            super.visitLdcInsn(new Long(value));
        }
    }

    public void push(float value) {
        if (value == 0f || value == 1f || value == 2f) {
            super.visitInsn(TypeUtils.FCONST(value));
        } else {
            super.visitLdcInsn(new Float(value));
        }
    }

    public void push(double value) {
        if (value == 0d || value == 1d) {
            super.visitInsn(TypeUtils.DCONST(value));
        } else {
            super.visitLdcInsn(new Double(value));
        }
    }

    public void push(String value) {
        super.visitLdcInsn(value);
    }

    public void push(Type type) {
        Wrapper wrapper = Wrapper.findPrimitiveType(type);
        if (wrapper != null && Wrapper.DEFAULT != wrapper) {
            getstatic(wrapper.wrapperType(), "TYPE", Type.getType(Class.class));
            return;
        }
        super.visitLdcInsn(type);
    }

    public void newarray() {
        newarray(Constants.T_OBJECT);
    }

    public void newarray(Type type) {
        if (TypeUtils.isPrimitive(type)) {
            super.visitIntInsn(ASMOpcodes.NEWARRAY, TypeUtils.NEWARRAY(type));
        } else {
            emit_type(ASMOpcodes.ANEWARRAY, type);
        }
    }

    public void newarray(Class<?> type) {
        newarray(Type.getType(type));
    }

    public void arraylength() {
        super.visitInsn(ASMOpcodes.ARRAYLENGTH);
    }

    public void load_this() {
        if (TypeUtils.isStatic(state.access)) {
            throw new IllegalStateException("no 'this' pointer within static method");
        }
        super.visitVarInsn(ASMOpcodes.ALOAD, 0);
    }

    /**
     * Pushes all of the arguments of the current method onto the stack.
     */
    public void load_args() {
        load_args(0, state.argumentTypes.length);
    }

    /**
     * Pushes the specified argument of the current method onto the stack.
     *
     * @param index the zero-based index into the argument list
     */
    public void load_arg(int index) {
        load_local(state.argumentTypes[index],
                state.localOffset + skipArgs(index));
    }

    // zero-based (see load_this)
    public void load_args(int fromArg, int count) {
        int pos = state.localOffset + skipArgs(fromArg);
        for (int i = 0; i < count; i++) {
            Type t = state.argumentTypes[fromArg + i];
            load_local(t, pos);
            pos += t.getSize();
        }
    }

    private int skipArgs(int numArgs) {
        int amount = 0;
        for (int i = 0; i < numArgs; i++) {
            amount += state.argumentTypes[i].getSize();
        }
        return amount;
    }

    private void load_local(Type t, int pos) {
        // TODO: make t == null ok?
        super.visitVarInsn(t.getOpcode(ASMOpcodes.ILOAD), pos);
    }

    private void store_local(Type t, int pos) {
        // TODO: make t == null ok?
        super.visitVarInsn(t.getOpcode(ASMOpcodes.ISTORE), pos);
    }

    public void iinc(Local local, int amount) {
        super.visitIincInsn(local.getIndex(), amount);
    }

    public void store_local(Local local) {
        store_local(local.getType(), local.getIndex());
    }

    public Local store_local(Type type) {
        Local local = make_local(type);
        store_local(local.getType(), local.getIndex());
        return local;
    }

    public Local store_local(Class<?> type) {
        Local local = make_local(Type.getType(type));
        store_local(local.getType(), local.getIndex());
        return local;
    }

    public void load_local(Local local) {
        load_local(local.getType(), local.getIndex());
    }

    public void return_value() {
        super.visitInsn(state.sig.getReturnType().getOpcode(ASMOpcodes.IRETURN));
    }

    public void getfield(String name) {
        ClassEmitter.FieldInfo info = ce.getFieldInfo(name);
        int opcode = TypeUtils.isStatic(info.access) ? ASMOpcodes.GETSTATIC : ASMOpcodes.GETFIELD;
        emit_field(opcode, ce.getClassType(), name, info.type);
    }

    public void putfield(String name) {
        ClassEmitter.FieldInfo info = ce.getFieldInfo(name);
        int opcode = TypeUtils.isStatic(info.access) ? ASMOpcodes.PUTSTATIC : ASMOpcodes.PUTFIELD;
        emit_field(opcode, ce.getClassType(), name, info.type);
    }

    public void super_getfield(String name, Type type) {
        emit_field(ASMOpcodes.GETFIELD, ce.getSuperType(), name, type);
    }

    public void super_putfield(String name, Type type) {
        emit_field(ASMOpcodes.PUTFIELD, ce.getSuperType(), name, type);
    }

    public void super_getstatic(String name, Type type) {
        emit_field(ASMOpcodes.GETSTATIC, ce.getSuperType(), name, type);
    }

    public void super_putstatic(String name, Type type) {
        emit_field(ASMOpcodes.PUTSTATIC, ce.getSuperType(), name, type);
    }

    public void getfield(Type owner, String name, Type type) {
        emit_field(ASMOpcodes.GETFIELD, owner, name, type);
    }

    public void putfield(Type owner, String name, Type type) {
        emit_field(ASMOpcodes.PUTFIELD, owner, name, type);
    }

    public void getstatic(Type owner, String name, Type type) {
        emit_field(ASMOpcodes.GETSTATIC, owner, name, type);
    }

    public void putstatic(Type owner, String name, Type type) {
        emit_field(ASMOpcodes.PUTSTATIC, owner, name, type);
    }

    // package-protected for EmitUtils, try to fix
    void emit_field(int opcode, Type ctype, String name, Type ftype) {
        super.visitFieldInsn(opcode,
                ctype.getInternalName(),
                name,
                ftype.getDescriptor());
    }

    public void super_invoke() {
        super_invoke(state.sig);
    }

    public void super_invoke(Signature sig) {
        emit_invoke(ASMOpcodes.INVOKESPECIAL, ce.getSuperType(), sig, false);
    }

    public void invoke_constructor(Type type) {
        invoke_constructor(type, CSTRUCT_NULL);
    }

    public void super_invoke_constructor() {
        invoke_constructor(ce.getSuperType());
    }

    public void invoke_constructor_this() {
        invoke_constructor(ce.getClassType());
    }

    private void emit_invoke(int opcode, Type type, Signature sig, boolean isInterface) {
        if (sig.getName().equals(Constants.NAME_CTOR) &&
                ((opcode == ASMOpcodes.INVOKEVIRTUAL) ||
                        (opcode == ASMOpcodes.INVOKESTATIC))) {
            // TODO: error
        }
        super.visitMethodInsn(opcode,
                type.getInternalName(),
                sig.getName(),
                sig.getDescriptor(),
                isInterface);
    }

    public void invoke_interface(Type owner, Signature sig) {
        emit_invoke(ASMOpcodes.INVOKEINTERFACE, owner, sig, true);
    }

    public void invoke_virtual(Type owner, Signature sig) {
        emit_invoke(ASMOpcodes.INVOKEVIRTUAL, owner, sig, false);
    }

    public void invoke_virtual(Type owner, String methodName, MethodType methodType) {
        emit_invoke(ASMOpcodes.INVOKEVIRTUAL, owner, new Signature(methodName, methodType.toMethodDescriptorString()), false);
    }

    public void invoke_virtual(Class<?> owner, String methodName, MethodType methodType) {
        invoke_virtual(owner, methodName, methodType.toMethodDescriptorString());
    }

    public void invoke_virtual(Class<?> owner, String methodName, String methodDescriptor) {
        emit_invoke(ASMOpcodes.INVOKEVIRTUAL, Type.getType(owner), new Signature(methodName,
                        methodDescriptor),
                false);
    }

    public void invoke_static(Type owner, Signature sig) {
        invoke_static(owner, sig, false);
    }

    public void invoke_static(Type owner, String methodName, MethodType methodType) {
        invoke_static(owner, new Signature(methodName, methodType.toMethodDescriptorString()), false);
    }

    public void invoke_static(Class<?> owner, String methodName, MethodType methodType) {
        invoke_static(Type.getType(owner), new Signature(methodName, methodType.toMethodDescriptorString()), false);
    }

    public void invoke_static(Type owner, Signature sig, boolean isInterface) {
        emit_invoke(ASMOpcodes.INVOKESTATIC, owner, sig, isInterface);
    }

    public void invoke_virtual_this(Signature sig) {
        invoke_virtual(ce.getClassType(), sig);
    }

    public void invoke_static_this(Signature sig) {
        invoke_static(ce.getClassType(), sig);
    }

    public void invoke_static_this(String methodName, MethodType methodType) {
        invoke_static(ce.getClassType(), methodName, methodType);
    }

    public void invoke_constructor(Type type, Signature sig) {
        emit_invoke(ASMOpcodes.INVOKESPECIAL, type, sig, false);
    }

    public void invoke_constructor(Type type, Class<?>... pTypes) {
        emit_invoke(ASMOpcodes.INVOKESPECIAL, type, new Signature(Constants.NAME_CTOR,
                        MethodType.methodType(Void.TYPE, pTypes).toMethodDescriptorString()),
                false);
    }

    public void invoke_constructor(Class<?> owner, Class<?>... pTypes) {
        emit_invoke(ASMOpcodes.INVOKESPECIAL, Type.getType(owner), new Signature(Constants.NAME_CTOR,
                        MethodType.methodType(Void.TYPE, pTypes).toMethodDescriptorString()),
                false);
    }

    public void invoke_constructor_this(Signature sig) {
        invoke_constructor(ce.getClassType(), sig);
    }

    public void invoke_constructor_this(MethodType methodType) {
        invoke_constructor(ce.getClassType(),
                new Signature(Constants.NAME_CTOR, methodType.changeReturnType(Void.TYPE).toMethodDescriptorString()));
    }

    public void super_invoke_constructor(Signature sig) {
        invoke_constructor(ce.getSuperType(), sig);
    }

    public void new_instance_this() {
        new_instance(ce.getClassType());
    }

    public void new_instance(Type type) {
        emit_type(ASMOpcodes.NEW, type);
    }

    public void new_instance(Class<?> type) {
        emit_type(ASMOpcodes.NEW, Type.getType(type));
    }

    private void emit_type(int opcode, Type type) {
        String desc;
        if (TypeUtils.isArray(type)) {
            desc = type.getDescriptor();
        } else {
            desc = type.getInternalName();
        }
        super.visitTypeInsn(opcode, desc);
    }

    public void aaload(int index) {
        push(index);
        aaload();
    }

    public void aaload() {
        super.visitInsn(ASMOpcodes.AALOAD);
    }

    public void aastore() {
        super.visitInsn(ASMOpcodes.AASTORE);
    }

    public void athrow() {
        super.visitInsn(ASMOpcodes.ATHROW);
    }

    public Label make_label() {
        return new Label();
    }

    public Local make_local() {
        return make_local(Constants.T_OBJECT);
    }

    public Local make_local(Type type) {
        return new Local(newLocal(type.getSize()), type);
    }

    public Local make_local(Class<?> clazz) {
        return make_local(Type.getType(clazz));
    }

    public void checkcast_this() {
        checkcast(ce.getClassType());
    }

    public void checkcast(Type type) {
        if (!type.equals(Constants.T_OBJECT)) {
            emit_type(ASMOpcodes.CHECKCAST, type);
        }
    }

    public void instance_of(Type type) {
        emit_type(ASMOpcodes.INSTANCEOF, type);
    }

    public void instance_of_this() {
        instance_of(ce.getClassType());
    }

    public void process_switch(int[] keys, ProcessSwitchCallback callback) {
        float density;
        if (keys.length == 0) {
            density = 0;
        } else {
            density = (float) keys.length / (keys[keys.length - 1] - keys[0] + 1);
        }
        process_switch(keys, callback, density >= 0.5f);
    }

    public void process_switch(int[] keys, ProcessSwitchCallback callback, boolean useTable) {
        if (!isSorted(keys))
            throw new IllegalArgumentException("keys to switch must be sorted ascending");
        Label def = make_label();
        Label end = make_label();

        try {
            if (keys.length > 0) {
                int len = keys.length;
                int min = keys[0];
                int max = keys[len - 1];
                int range = max - min + 1;

                if (useTable) {
                    Label[] labels = new Label[range];
                    Arrays.fill(labels, def);
                    for (int i = 0; i < len; i++) {
                        labels[keys[i] - min] = make_label();
                    }
                    super.visitTableSwitchInsn(min, max, def, labels);
                    for (int i = 0; i < range; i++) {
                        Label label = labels[i];
                        if (label != def) {
                            mark(label);
                            callback.processCase(i + min, end);
                        }
                    }
                } else {
                    Label[] labels = new Label[len];
                    for (int i = 0; i < len; i++) {
                        labels[i] = make_label();
                    }
                    super.visitLookupSwitchInsn(def, keys, labels);
                    for (int i = 0; i < len; i++) {
                        mark(labels[i]);
                        callback.processCase(keys[i], end);
                    }
                }
            }

            mark(def);
            callback.processDefault();
            mark(end);

        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    private static boolean isSorted(int[] keys) {
        for (int i = 1; i < keys.length; i++) {
            if (keys[i] < keys[i - 1])
                return false;
        }
        return true;
    }

    public void mark(Label label) {
        super.visitLabel(label);
    }

    Label mark() {
        Label label = make_label();
        super.visitLabel(label);
        return label;
    }

    public void push(boolean value) {
        push(value ? 1 : 0);
    }

    /**
     * Toggles the integer on the top of the stack from 1 to 0 or vice versa
     */
    public void not() {
        push(1);
        math(XOR, Type.INT_TYPE);
    }

    public void throw_exception(Type type, String msg) {
        new_instance(type);
        dup();
        push(msg);
        invoke_constructor(type, CSTRUCT_STRING);
        athrow();
    }

    /**
     * If the argument is a primitive class, replaces the primitive value
     * on the top of the stack with the wrapped (Object) equivalent. For
     * example, char -> Character.
     * If the class is Void, a null is pushed onto the stack instead.
     *
     * @param type the class indicating the current type of the top stack value
     */
    public void box(Type type) {
        if (Wrapper.isPrimitiveType(type)) {
            if (type == Type.VOID_TYPE) {
                aconst_null();
            } else {
                Wrapper wrapper = Wrapper.forPrimitiveType(type);
                invoke_static(wrapper.wrapperType(), new Signature("valueOf",
                        Type.getMethodDescriptor(wrapper.wrapperType(), wrapper.primitiveType())));
            }
        }
    }

    public void box(Class<?> type) {
        box(Type.getType(type));
    }

    /**
     * If the argument is a primitive class, replaces the object
     * on the top of the stack with the unwrapped (primitive)
     * equivalent. For example, Character -> char.
     *
     * @param type the class indicating the desired type of the top stack value
     * @return true if the value was unboxed
     */
    public void unbox(Type type) {
        Wrapper wrapper = Wrapper.findPrimitiveType(type);
        if (wrapper == null || Wrapper.VOID == wrapper) {
            return;
        }
        if (wrapper == Wrapper.DEFAULT) {
            checkcast(type);
            return;
        }
        checkcast(wrapper.wrapperType());
        invoke_virtual(wrapper.wrapperType(), new Signature(wrapper.primitiveSimpleName() + "Value",
                Type.getMethodDescriptor(wrapper.primitiveType(), wrapper.wrapperType())));
    }

    /**
     * Allocates and fills an Object[] array with the arguments to the
     * current method. Primitive values are inserted as their boxed
     * (Object) equivalents.
     */
    public void create_arg_array() {
        /* generates:
           Object[] args = new Object[]{ arg1, new Integer(arg2) };
         */

        push(state.argumentTypes.length);
        newarray();
        for (int i = 0; i < state.argumentTypes.length; i++) {
            dup();
            push(i);
            load_arg(i);
            box(state.argumentTypes[i]);
            aastore();
        }
    }

    /**
     * Pushes a zero onto the stack if the argument is a primitive class, or a null otherwise.
     */
    public void zero_or_null(Type type) {
        if (TypeUtils.isPrimitive(type)) {
            switch (type.getSort()) {
                case Type.DOUBLE:
                    push(0d);
                    break;
                case Type.LONG:
                    push(0L);
                    break;
                case Type.FLOAT:
                    push(0f);
                    break;
                case Type.VOID:
                    aconst_null();
                default:
                    push(0);
            }
        } else {
            aconst_null();
        }
    }

    /**
     * Unboxes the object on the top of the stack. If the object is null, the
     * unboxed primitive value becomes zero.
     */
    public void unbox_or_zero(Type type) {
        if (TypeUtils.isPrimitive(type)) {
            if (type != Type.VOID_TYPE) {
                Label nonNull = make_label();
                Label end = make_label();
                dup();
                ifnonnull(nonNull);
                pop();
                zero_or_null(type);
                goTo(end);
                mark(nonNull);
                unbox(type);
                mark(end);
            }
        } else {
            checkcast(type);
        }
    }

    public void visitMaxs(int maxStack, int maxLocals) {
        if (!TypeUtils.isAbstract(state.access)) {
            super.visitMaxs(0, 0);
        }
    }

    public void invoke(MethodInfo method, Type virtualType) {
        ClassInfo classInfo = method.getClassInfo();
        Type type = classInfo.getType();
        Signature sig = method.getSignature();
        if (sig.getName().equals(Constants.NAME_CTOR)) {
            invoke_constructor(type, sig);
        } else if (TypeUtils.isStatic(method.getModifiers())) {
            invoke_static(type, sig, TypeUtils.isInterface(classInfo.getModifiers()));
        } else if (TypeUtils.isInterface(classInfo.getModifiers())) {
            invoke_interface(type, sig);
        } else {
            invoke_virtual(virtualType, sig);
        }
    }

    public void invoke(MethodInfo method) {
        invoke(method, method.getClassInfo().getType());
    }
}
